/*
  * Copyright (c) 2015 Cryptonomex, Inc., and contributors.
  *
  * The MIT License
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
#pragma once

#include <hive/app/plugin.hpp>
#include <hive/chain/database.hpp>

#include <boost/multi_index/composite_key.hpp>

#include <fc/thread/future.hpp>
#include <fc/api.hpp>

namespace hive { namespace private_message {
using namespace chain;
using app::application;

//
// Plugins should #define their SPACE_ID's so plugins with
// conflicting SPACE_ID assignments can be compiled into the
// same binary (by simply re-assigning some of the conflicting #defined
// SPACE_ID's in a build script).
//
// Assignment of SPACE_ID's cannot be done at run-time because
// various template automagic depends on them being known at compile
// time.
//
#ifndef HIVE_PRIVATE_MESSAGE_SPACE_ID
#define HIVE_PRIVATE_MESSAGE_SPACE_ID 6
#endif

#define HIVE_PRIVATE_MESSAGE_COP_ID 777

enum private_message_object_type
{
  message_object_type = ( HIVE_PRIVATE_MESSAGE_SPACE_ID << 8 )
};


namespace detail
{
    class private_message_plugin_impl;
}

struct message_body
{
    fc::time_point    thread_start; /// the sent_time of the original message, if any
    string            subject;
    string            body;
    string            json_meta;
    flat_set<string>  cc;
};



class message_object : public object< message_object_type, message_object >
{
  CHAINBASE_OBJECT( message_object );
  public:
    CHAINBASE_DEFAULT_CONSTRUCTOR( message_object, (encrypted_message) )

    account_name_type from;
    account_name_type to;
    public_key_type   from_memo_key;
    public_key_type   to_memo_key;
    uint64_t          sent_time = 0; /// used as seed to secret generation
    time_point_sec    receive_time; /// time received by blockchain
    uint32_t          checksum = 0;
    buffer_type       encrypted_message;
};

typedef oid_ref< message_object > message_id_type;

struct message_api_obj
{
  message_api_obj( const message_object& o ) :
    id( o.id ),
    from( o.from ),
    to( o.to ),
    from_memo_key( o.from_memo_key ),
    to_memo_key( o.to_memo_key ),
    sent_time( o.sent_time ),
    receive_time( o.receive_time ),
    checksum( o.checksum ),
    encrypted_message( o.encrypted_message.begin(), o.encrypted_message.end() )
  {}

  message_api_obj(){}

  message_id_type   id;
  account_name_type from;
  account_name_type to;
  public_key_type   from_memo_key;
  public_key_type   to_memo_key;
  uint64_t          sent_time;
  time_point_sec    receive_time;
  uint32_t          checksum;
  vector< char >    encrypted_message;
};

struct extended_message_object : public message_api_obj
{
  extended_message_object() {}
  extended_message_object( const message_api_obj& o ):message_api_obj( o ) {}

  message_body   message;
};

struct by_to_date;
struct by_from_date;

using namespace boost::multi_index;

typedef multi_index_container<
  message_object,
  indexed_by<
    ordered_unique< tag< by_id >, member< message_object, message_object::id_type, &message_object::id > >,
    ordered_unique< tag< by_to_date >,
        composite_key< message_object,
          member< message_object, account_name_type, &message_object::to >,
          member< message_object, time_point_sec, &message_object::receive_time >,
          member< message_object, message_object::id_type, &message_object::id >
        >,
        composite_key_compare< std::less< string >, std::greater< time_point_sec >, std::less< message_id_type > >
    >,
    ordered_unique< tag< by_from_date >,
        composite_key< message_object,
          member< message_object, account_name_type, &message_object::from >,
          member< message_object, time_point_sec, &message_object::receive_time >,
          member< message_object, message_object::id_type, &message_object::id >
        >,
        composite_key_compare< std::less< string >, std::greater< time_point_sec >, std::less< message_id_type > >
    >
  >,
  allocator< message_object >
> message_index;


/**
  *   This plugin scans the blockchain for custom operations containing a valid message and authorized
  *   by the posting key.
  *
  */
class private_message_plugin : public hive::app::plugin
{
  public:
    private_message_plugin( application* app );
    virtual ~private_message_plugin();

    std::string plugin_name()const override;
    virtual void plugin_set_program_options(
      boost::program_options::options_description& cli,
      boost::program_options::options_description& cfg) override;
    virtual void plugin_initialize(const boost::program_options::variables_map& options) override;
    virtual void plugin_startup() override;

    flat_map<string,string> tracked_accounts()const; /// map start_range to end_range

    friend class detail::private_message_plugin_impl;
    std::unique_ptr<detail::private_message_plugin_impl> my;
};

class private_message_api : public std::enable_shared_from_this<private_message_api> {
  public:
    private_message_api(){};
    private_message_api(const app::api_context& ctx):_app(&ctx.app){
      ilog( "creating private message api" );
    }
    void on_api_startup(){
      wlog( "on private_message api startup" );
    }

    /**
      *
      */
    vector< message_api_obj > get_inbox( string to, time_point newest, uint16_t limit )const;
    vector< message_api_obj > get_outbox( string from, time_point newest, uint16_t limit )const;

  private:
    app::application* _app = nullptr;
};

} } //hive::private_message

FC_API( hive::private_message::private_message_api, (get_inbox)(get_outbox) );

FC_REFLECT( hive::private_message::message_body, (thread_start)(subject)(body)(json_meta)(cc) );

FC_REFLECT( hive::private_message::message_object, (id)(from)(to)(from_memo_key)(to_memo_key)(sent_time)(receive_time)(checksum)(encrypted_message) );
CHAINBASE_SET_INDEX_TYPE( hive::private_message::message_object, hive::private_message::message_index );

FC_REFLECT( hive::private_message::message_api_obj, (id)(from)(to)(from_memo_key)(to_memo_key)(sent_time)(receive_time)(checksum)(encrypted_message) );

FC_REFLECT_DERIVED( hive::private_message::extended_message_object, (hive::private_message::message_api_obj), (message) );
